#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Author  ：彩纸penguins
@Date    ：2025/4/25

WDMtoWNTR类 - 将WaterDesk模型(wdm)转换为WNTR水力模型
'''

import os
import uuid
import sqlite3
import pandas as pd
import numpy as np
import wntr
from tqdm import tqdm
from colorama import Fore, Style, init

# 初始化colorama
init(autoreset=True)

# 定义颜色常量
WARNING = Fore.YELLOW + Style.BRIGHT  # 警告信息
ERROR = Fore.RED + Style.BRIGHT       # 错误信息
INFO = Fore.CYAN                      # 一般信息
STEP = Fore.GREEN                     # 步骤进展
SUCCESS = Fore.GREEN + Style.BRIGHT   # 成功信息
DEBUG = Fore.WHITE + Style.DIM        # 调试信息

class waterdesk_model:
    """
    waterdesk_model类 - 加载WaterDesk模型数据库
    
    参数:
        wdm_file_path (str): WaterDesk模型文件(.wdm)的路径
    
    示例:
        >>> waterdesk_model = waterdesk_model("example.wdm")
        >>> wn =waterdesk_model.to_wntr()
        >>> waterdesk_model.to_inp(filename='output.inp', version=2.2)
    """

    def __init__(self, wdm_file_path: str):
        # 传入数据类型判断
        if not isinstance(wdm_file_path, str):
            raise TypeError("wdm文件地址必须为字符串类型")
        self.wdm_file_path = wdm_file_path
        self.read_wdm_creat_dataframe()
        self.options_dict = self.Options_df.set_index('Property')['Data'].to_dict()
        print(INFO + f"{self.options_dict}")
        self.units_change()

    def check_df_empty(self, df: pd.DataFrame, df_name: str):
        """检查传入数据表是否为空"""
        if df.empty:
            print(INFO + f"{df_name}表为空，可能需要处理。")
            return False
        else:
            print(INFO + f"{df_name}表不为空，继续处理。")
            return True

    def read_wdm_creat_dataframe(self):
        import sqlite3

        # 连接到wdm数据库，读取与建模有关的数据表
        wdm_conn = sqlite3.connect(self.wdm_file_path)
        cursor = wdm_conn.cursor()

        # 定义要读取的表名和对应的属性名
        tables = [
            ("Junctions", "Junctions_df"),
            ("Reservoirs", "Reservoirs_df"),
            ("Tanks", "Tanks_df"),
            ("Pipes", "Pipes_df"),
            ("Pumps", "Pumps_df"),
            ("Valves", "Valves_df"),
            ("TimePatterns", "TimePatterns_df"),
            ("Demands", "Demands_df"),
            ("WaterMeters", "WaterMeters_df"),
            ("DemandRegions", "DemandRegions_df"),
            ("Vertices", "Vertices_df"),
            ("Controls", "Controls_df"),
            ("Curves", "Curves_df"),
            ("Options", "Options_df")
        ]

        for table_name, attr_name in tables:
            # 检查表是否存在
            cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
            if cursor.fetchone():
                # 表存在，读取数据
                setattr(self, attr_name, pd.read_sql_query(f"SELECT * FROM {table_name}", wdm_conn))
            else:
                print(INFO + f"{table_name} 表不存在，跳过读取。")
                setattr(self, attr_name, pd.DataFrame())  # 初始化空 DataFrame

        # 关闭数据库连接
        wdm_conn.close()

        def _check_WaterMeters():
            """处理WaterMeters_df表的模式"""
            # 检查WaterMeters_df表、DemandRegions_df表是否为空
            is_WaterMeters_df = self.check_df_empty(self.WaterMeters_df, "WaterMeters")
            is_DemandRegions_df = self.check_df_empty(self.DemandRegions_df, "DemandRegions")

            if is_WaterMeters_df == True and is_DemandRegions_df == True:
                # 这意味着需要分区调整用水量

                print(STEP + "正在处理WaterMeters_df表的模式...")
                Junctions_df = self.Junctions_df.copy()
                DemandRegions_df = self.DemandRegions_df.copy()
                WaterMeters_df = self.WaterMeters_df.copy()

                # 1. 将WaterMeters_df中的JunctionID与Junctions_df的Id进行匹配
                # 创建一个字典，用于存储节点ID与其所属区域的映射关系
                junction_to_region = {}
                for _, row in Junctions_df.iterrows():
                    junction_to_region[str(row['Id'])] = str(row['Region']) if not pd.isna(row['Region']) else None

                # 将区域信息添加到WaterMeters_df
                WaterMeters_df['Region'] = WaterMeters_df['JunctionID'].map(junction_to_region)

                # 2. 将区域信息与DemandRegions_df的Id进行匹配
                # 创建一个字典，用于存储区域ID与其需水量比例和系统模式的映射关系
                region_to_info = {}
                for _, row in DemandRegions_df.iterrows():
                    region_to_info[str(row['Id'])] = {
                        'GeneralCustomerDemandRatio': float(row['GeneralCustomerDemandRatio']) if not pd.isna(
                            row['GeneralCustomerDemandRatio']) else 0.0,
                        'SystemPatternId': str(row['SystemPatternId']) if not pd.isna(row['SystemPatternId']) else None
                    }

                # 将需水量比例和系统模式信息添加到WaterMeters_df
                # 修改 GeneralCustomerDemandRatio 的映射逻辑，当 Region 为 None 时赋值为 1.0
                WaterMeters_df['GeneralCustomerDemandRatio'] = WaterMeters_df['Region'].map(
                    lambda x: region_to_info.get(x, {}).get('GeneralCustomerDemandRatio', 1.0) if x else 1.0
                )
                WaterMeters_df['SystemPatternId'] = WaterMeters_df['Region'].map(
                    lambda x: region_to_info.get(x, {}).get('SystemPatternId', None) if x else None
                )

                # 3. 检查GeneralCustomerDemandRatio是否为空或0
                empty_ratio_count = len(WaterMeters_df[
                                            (WaterMeters_df['GeneralCustomerDemandRatio'].isna()) |
                                            (WaterMeters_df['GeneralCustomerDemandRatio'] == 0.0)
                                            ])
                if empty_ratio_count > 0:
                    print(
                        WARNING + f"发现 {empty_ratio_count} 条记录的GeneralCustomerDemandRatio为空或0，这些记录的需水量将被设置为0")

                # 4. 更新MeterDemand和Pattern
                # 更新MeterDemand：乘以GeneralCustomerDemandRatio
                WaterMeters_df['MeterDemand'] = WaterMeters_df.apply(
                    lambda row: float(row['MeterDemand']) * row['GeneralCustomerDemandRatio']
                    if not pd.isna(row['MeterDemand']) and not pd.isna(row['GeneralCustomerDemandRatio'])
                    else 0.0,
                    axis=1
                )

                # 更新Pattern：使用SystemPatternId
                WaterMeters_df['Pattern'] = WaterMeters_df['SystemPatternId']

                # 5. 删除临时列
                self.real_WaterMeters_df = WaterMeters_df.drop(
                    ['Region', 'GeneralCustomerDemandRatio', 'SystemPatternId'], axis=1)

                print(SUCCESS + f"WaterMeters_df表处理完成，共处理 {len(WaterMeters_df)} 条记录")
                print(DEBUG + f"处理后的WaterMeters_df表前3行:\n{WaterMeters_df.head(3)}")

                # 合并Demands_df和WaterMeters_df
                Demands_selected_df = self.Demands_df[['JunctionId', 'Demand', 'Pattern']].copy()
                print(DEBUG + f"len(Demands_selected_df): {len(Demands_selected_df)}")
                WaterMeters_selected_df = self.real_WaterMeters_df[['JunctionID', 'MeterDemand', 'Pattern']].copy()
                print(DEBUG + f"len(WaterMeters_selected_df): {len(WaterMeters_selected_df)}")
                # 重命名列名
                WaterMeters_selected_df.rename(columns={'JunctionID': 'JunctionId', 'MeterDemand': 'Demand'},
                                               inplace=True)

                self.external_demand_df = pd.concat([Demands_selected_df, WaterMeters_selected_df], ignore_index=True)
                print(DEBUG + f"len(self.external_demand_df): {len(self.external_demand_df)}")
                self.external_demand_df.to_excel('external_demand_df.xlsx', index=False)

            if is_WaterMeters_df == True and is_DemandRegions_df == False:
                # 不需要分区调整用水量
                WaterMeters_df = self.WaterMeters_df.copy()
                # 重命名列名
                WaterMeters_df.rename(columns={'JunctionID': 'JunctionId', 'MeterDemand': 'Demand'}, inplace=True)
                self.external_demand_df = WaterMeters_df[['JunctionId', 'Demand', 'Pattern']].copy()

        _check_WaterMeters()

        def _check_id_length():
            """
            检查各个数据表中的ID是否超过32字节
            """
            # 在WaterDesk 5.0.0版本需要检查的表和对应的列名
            tables_to_check = [
                (self.Junctions_df, 'Id', '节点组件'),
                (self.Pipes_df, 'Id', '管道组件'),
                (self.Pumps_df, 'Id', '水泵组件'),
                (self.Reservoirs_df, 'Id', '水库组件'),
                (self.Tanks_df, 'Id', '水箱组件'),
                (self.Valves_df, 'Id', '阀门组件')
            ]

            for df, column, table in tables_to_check:
                if any(len(id_.encode()) > 32 for id_ in df[column]):
                    print(
                        ERROR + f"发现WaterDesk模型中{table}的ID超过32位，本工具包许多方法依赖wntr库，而wntr库要求组件ID不能操作32位，建议在read_wdm_to_dataframe方法之后，执行map_id_to_newid方法，该方法会自动进行ID映射，并保存映射表到本地数据库id_to_newid.db或id_to_newid.xlsx")
                    break

        _check_id_length()

    def map_id_to_newid(self, save_mapping_type: str = 'database'):
        """
        对节点、管道、水泵、阀门、水箱、水库的ID进行映射，映射后的ID长度不超过32字节
        :param save_mapping_type: 映射表的保存文件类型，可选xlsx、db、database。默认命名 'id_to_newid'
        :return: waterdesk_model 对象
        """
        import random
        import hashlib
        import re

        def _map():
            def _generate_reproducible_new_id(id_str, seed):
                random.seed(seed)
                hash_obj = hashlib.sha256((id_str + str(seed)).encode())
                short_new_id = hash_obj.hexdigest()[:16]
                return short_new_id

            # 随机生成一个随机数种子，之后的代码都统一使用这个随机数种子
            seed = random.randint(0, 10086)

            def _map_nodes(table_df, table_name):
                """映射节点类组件（节点、水箱、水库）的ID"""
                print(STEP + f"正在映射 {table_name} 的ID...")
                # 获取所有旧ID
                old_ids = table_df['Id'].values
                # 向量化生成新ID
                new_ids = pd.Series(old_ids).apply(lambda x: _generate_reproducible_new_id(x, seed))
                # 批量更新DataFrame
                table_df['Id'] = new_ids
                # 创建映射DataFrame
                map_df = pd.DataFrame({
                    'id': old_ids,
                    'new_id': new_ids,
                    'seed': seed
                })
                return table_df, map_df

            # 映射所有点类型组件
            self.Junctions_df, self.Junctions_map_df = _map_nodes(self.Junctions_df, '节点')
            self.Reservoirs_df, self.Reservoirs_map_df = _map_nodes(self.Reservoirs_df, '水库')
            self.Tanks_df, self.Tanks_map_df = _map_nodes(self.Tanks_df, '水箱')

            # 创建节点映射字典
            nodes_map_dict = {}
            # 遍历所有节点类型的DataFrame
            for df in [self.Junctions_map_df, self.Tanks_map_df, self.Reservoirs_map_df]:
                # 遍历每个DataFrame中的所有行
                for _, row in df.iterrows():
                    nodes_map_dict[row['id']] = row['new_id']

            def _map_links(table_df, table_name, nodes_map_dict):
                """映射连接类组件（管道、水泵、阀门）的ID和连接节点"""
                print(STEP + f"正在映射 {table_name} 的ID...")
                # 获取所有旧ID并生成新ID
                old_ids = table_df['Id'].values
                new_ids = pd.Series(old_ids).apply(lambda x: _generate_reproducible_new_id(x, seed))
                # 批量更新组件ID
                table_df['Id'] = new_ids
                # 批量更新起始和终止节点
                for node_field in ['StartNodeId', 'EndNodeId']:
                    # 使用map方法进行批量映射
                    new_node_ids = table_df[node_field].map(nodes_map_dict)
                    # 检查映射失败的节点
                    missing_nodes = table_df[new_node_ids.isna()][node_field]
                    if not missing_nodes.empty:
                        for old_id, node_id in zip(table_df.loc[missing_nodes.index, 'Id'], missing_nodes):
                            print(
                                WARNING + f"错误: 组件{table_name} {old_id} 的{node_field} {node_id} 没有找到映射关系")
                    # 更新节点ID
                    table_df[node_field] = new_node_ids
                # 创建映射DataFrame
                map_df = pd.DataFrame({
                    'id': old_ids,
                    'new_id': new_ids,
                    'seed': seed
                })
                return table_df, map_df

            self.Pipes_df, self.Pipes_map_df = _map_links(self.Pipes_df, '管道', nodes_map_dict)
            self.Pumps_df, self.Pumps_map_df = _map_links(self.Pumps_df, '水泵', nodes_map_dict)
            self.Valves_df, self.Valves_map_df = _map_links(self.Valves_df, '阀门', nodes_map_dict)

            def _map_demands(nodes_map_dict):
                """映射额外需水量的ID"""
                external_demand_df = self.external_demand_df.copy()

                print(STEP + "正在映射额外需水量的ID...")
                # 获取所有旧ID并生成新ID
                # 检查external_demand_df是否为空
                if external_demand_df.empty:
                    print(INFO + "额外需水量表为空，跳过映射")
                    return

                # 映射JunctionId列
                new_junction_ids = external_demand_df['JunctionId'].map(nodes_map_dict)

                # 检查映射失败的节点
                missing_nodes = external_demand_df[new_junction_ids.isna()]['JunctionId']
                if not missing_nodes.empty:
                    for junction_id in missing_nodes:
                        print(WARNING + f"警告: 额外需水量表中的节点ID {junction_id} 没有找到映射关系")

                # 更新JunctionId列
                external_demand_df['JunctionId'] = new_junction_ids
                self.external_demand_df = external_demand_df

                print(SUCCESS + f"额外需水量ID映射完成，共映射 {len(new_junction_ids) - len(missing_nodes)} 个ID")

            _map_demands(nodes_map_dict)

            def _map_vertices():
                """映射折点的ID"""
                print(STEP + "正在映射折点的ID...")
                # 检查Vertices_df是否为空
                if self.Vertices_df.empty:
                    print(INFO + "折点表为空，跳过映射")
                    return

                # 创建链接映射
                links_map_dict = {}
                for df in [self.Pipes_map_df, self.Pumps_map_df, self.Valves_map_df]:
                    for _, row in df.iterrows():
                        links_map_dict[row['id']] = row['new_id']

                # 映射Id列
                new_ids = self.Vertices_df['Id'].map(links_map_dict)
                # 检查映射失败的链接
                missing_links = self.Vertices_df[new_ids.isna()]['Id']
                if not missing_links.empty:
                    for link_id in missing_links:
                        print(WARNING + f"警告: 折点表中的链接ID {link_id} 没有找到映射关系")
                # 更新Id列
                self.Vertices_df['Id'] = new_ids
                print(SUCCESS + f"折点ID映射完成，共映射 {len(new_ids) - len(missing_links)} 个ID")

            _map_vertices()

            # 对控制规则进行映射
            def _map_control_rules():
                """将控制规则中的组件ID映射为new_id"""
                # 合并所有映射关系
                id_map = {}
                for df in [self.Junctions_map_df, self.Reservoirs_map_df, self.Tanks_map_df, self.Pipes_map_df,
                           self.Pumps_map_df, self.Valves_map_df]:
                    id_map.update(dict(zip(df['id'], df['new_id'])))

                print(STEP + "正在对控制规则进行分类和映射...")

                # 将控制规则拆分成列表
                self.Controls_df['split_control'] = self.Controls_df['Control'].str.split()

                # 根据第4个元素是否为'IF'分类
                # 处理IF控制规则
                if_controls = self.Controls_df[
                    self.Controls_df['split_control'].apply(lambda x: len(x) > 3 and x[3] == 'IF')].copy()

                if not if_controls.empty:
                    # 提取link和node ID
                    if_controls.loc[:, 'linkID'] = if_controls['split_control'].apply(lambda x: x[1])
                    if_controls.loc[:, 'nodeID'] = if_controls['split_control'].apply(lambda x: x[5])

                    # 映射ID
                    if_controls.loc[:, 'linkID'] = if_controls['linkID'].map(id_map)
                    if_controls.loc[:, 'nodeID'] = if_controls['nodeID'].map(id_map)

                    # 重建控制规则，如果映射失败则保留原始规则
                    def rebuild_if_control(row):
                        if pd.isna(row['linkID']) or pd.isna(row['nodeID']):
                            return ' '.join(row['split_control'])
                        parts = row['split_control'].copy()
                        parts[1] = str(row['linkID'])
                        parts[5] = str(row['nodeID'])
                        return ' '.join(parts)

                    if_controls.loc[:, 'Control'] = if_controls.apply(rebuild_if_control, axis=1)

                # 处理TIME控制规则
                time_controls = self.Controls_df[
                    self.Controls_df['split_control'].apply(lambda x: len(x) > 4 and x[4] == 'TIME')].copy()

                if not time_controls.empty:
                    # 提取link ID
                    time_controls.loc[:, 'linkID'] = time_controls['split_control'].apply(lambda x: x[1])

                    # 映射ID
                    time_controls.loc[:, 'linkID'] = time_controls['linkID'].map(id_map)

                    # 重建控制规则，如果映射失败则保留原始规则
                    def rebuild_time_control(row):
                        if pd.isna(row['linkID']):
                            return ' '.join(row['split_control'])
                        parts = row['split_control'].copy()
                        parts[1] = str(row['linkID'])
                        return ' '.join(parts)

                    time_controls.loc[:, 'Control'] = time_controls.apply(rebuild_time_control, axis=1)

                # 合并结果
                self.Controls_df = pd.concat([if_controls, time_controls])

            _map_control_rules()

            # 映射选项中的ID
            def _map_options(options_map_dict):
                """映射选项中的ID"""
                # 映射 TraceNode
                trace_node_id = self.options_dict['TraceNode']
                if trace_node_id in options_map_dict:
                    self.options_dict['TraceNode'] = options_map_dict[trace_node_id]
                else:
                    print(WARNING + "警告: TraceNode {trace_node_id} 没有找到映射关系，保持原值")

            _map_options(nodes_map_dict)

        def _save_mapping(save_mapping_type):
            """
            根据save_mapping_type参数选择保存映射表的方式
            """

            def _save_mapping_to_excel():
                """
                将映射表保存为Excel文件
                """
                with pd.ExcelWriter('id_to_newid.xlsx') as writer:
                    self.Junctions_map_df.to_excel(writer, sheet_name='Junctions',
                                                   index=False) if not self.Junctions_map_df.empty else None
                    self.Reservoirs_map_df.to_excel(writer, sheet_name='Reservoirs',
                                                    index=False) if not self.Reservoirs_map_df.empty else None
                    self.Tanks_map_df.to_excel(writer, sheet_name='Tanks',
                                               index=False) if not self.Tanks_map_df.empty else None
                    self.Pipes_map_df.to_excel(writer, sheet_name='Pipes',
                                               index=False) if not self.Pipes_map_df.empty else None
                    self.Pumps_map_df.to_excel(writer, sheet_name='Pumps',
                                               index=False) if not self.Pumps_map_df.empty else None
                    self.Valves_map_df.to_excel(writer, sheet_name='Valves',
                                                index=False) if not self.Valves_map_df.empty else None

            def _save_mapping_to_database():
                """
                将映射表保存为SQLite数据库
                """
                conn_id_to_uid = sqlite3.connect(f'id_to_newid.db')
                # 将映射关系保存到 id_to_uid.db
                self.Junctions_map_df.to_sql('Junctions', conn_id_to_uid, if_exists='replace',
                                             index=False) if not self.Junctions_map_df.empty else None
                self.Reservoirs_map_df.to_sql('Reservoirs', conn_id_to_uid, if_exists='replace',
                                              index=False) if not self.Reservoirs_map_df.empty else None
                self.Tanks_map_df.to_sql('Tanks', conn_id_to_uid, if_exists='replace',
                                         index=False) if not self.Tanks_map_df.empty else None
                self.Pipes_map_df.to_sql('Pipes', conn_id_to_uid, if_exists='replace',
                                         index=False) if not self.Pipes_map_df.empty else None
                self.Pumps_map_df.to_sql('Pumps', conn_id_to_uid, if_exists='replace',
                                         index=False) if not self.Pumps_map_df.empty else None
                self.Valves_map_df.to_sql('Valves', conn_id_to_uid, if_exists='replace',
                                          index=False) if not self.Valves_map_df.empty else None
                conn_id_to_uid.commit()
                conn_id_to_uid.close()

            if save_mapping_type == 'xlsx':
                _save_mapping_to_excel()
            if save_mapping_type == 'db' or save_mapping_type == 'database':
                _save_mapping_to_database()

        # 执行私有函数
        _map()
        _save_mapping(save_mapping_type)
        print(SUCCESS + f"ID映射完成，映射表已保存到本地数据库id_to_newid.{save_mapping_type}")

    def units_change(self):
        """
        进行单位换算，将WaterDesk模型中的数据单位转换为WNTR库使用的单位体系

        WaterDesk/EPANET支持的流量单位:
        - 美制单位: CFS(立方英尺/秒), GPM(加仑/分), MGD(百万加仑/日), IMGD(英制百万加仑/日), AFD(英亩英尺/日)
        - 公制单位: LPS(升/秒), LPM(升/分), MLD(兆升/日), CMH(立方米/小时), CMD(立方米/日)

        WNTR默认使用SI单位制(米、立方米/秒等)

        此方法会根据self.options_dict['FlowUnits']自动检测当前单位并进行相应转换
        """
        # 获取当前流量单位
        flow_units = str(self.options_dict['FlowUnits'])
        print(INFO + "输入文件的流量单位: {flow_units}")

        # 定义单位转换系数
        # 流量单位转换为立方米/秒(WNTR内部使用的单位)
        flow_conversion = {
            # 美制单位
            'CFS': 0.028316847,  # 立方英尺/秒 -> 立方米/秒
            'GPM': 0.000063090,  # 加仑/分钟 -> 立方米/秒
            'MGD': 0.043812636,  # 百万加仑/日 -> 立方米/秒
            'IMGD': 0.052616668,  # 英制百万加仑/日 -> 立方米/秒
            'AFD': 0.014276423,  # 英亩英尺/日 -> 立方米/秒

            # 公制单位
            'LPS': 0.001,  # 升/秒 -> 立方米/秒
            'LPM': 0.000016667,  # 升/分钟 -> 立方米/秒
            'MLD': 0.011574074,  # 兆升/日 -> 立方米/秒
            'CMH': 0.000277778,  # 立方米/小时 -> 立方米/秒
            'CMD': 0.000011574  # 立方米/日 -> 立方米/秒
        }

        # 长度单位转换为米(WNTR内部使用的单位)
        length_conversion = 0.3048 if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD'] else 1.0  # 英尺->米 或 米->米

        # 压力单位转换为米水头(WNTR内部使用的单位)
        pressure_conversion = 0.703070 if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD',
                                                         'AFD'] else 1.0  # psi->米水头 或 米水头->米水头

        # 直径单位转换为米(WNTR内部使用的单位)
        diameter_conversion = 0.0254 if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD'] else 0.001  # 英寸->米 或 毫米->米

        # 粗糙度单位转换(取决于水头损失公式)
        headloss_formula = self.options_dict.get('HeadlossFormular', 'H_W')  # 默认为Hazen-Williams

        # 转换节点数据
        if not self.Junctions_df.empty:
            print(STEP + "正在进行节点数据换算...")
            # 转换高程(英尺->米 或 米->米)
            self.Junctions_df['Elevation'] = self.Junctions_df['Elevation'] * length_conversion
            # 转换基本需水量(取决于流量单位)
            if flow_units in flow_conversion:
                self.Junctions_df['BaseDemand'] = self.Junctions_df['BaseDemand'] * flow_conversion[flow_units]
                self.external_demand_df['Demand'] = self.external_demand_df['Demand'] * flow_conversion[flow_units]

        # 转换水库数据
        if not self.Reservoirs_df.empty:
            print(STEP + "正在进行水库数据换算...")
            # 转换总水头(英尺->米 或 米->米)
            self.Reservoirs_df['TotalHead'] = self.Reservoirs_df['TotalHead'] * length_conversion

        # 转换水箱数据
        if not self.Tanks_df.empty:
            print(STEP + "正在进行水箱数据换算...")
            # 转换高程和水位(英尺->米 或 米->米)
            self.Tanks_df['Elevation'] = self.Tanks_df['Elevation'] * length_conversion
            self.Tanks_df['InitialLevel'] = self.Tanks_df['InitialLevel'] * length_conversion
            self.Tanks_df['MinLevel'] = self.Tanks_df['MinLevel'] * length_conversion
            self.Tanks_df['MaxLevel'] = self.Tanks_df['MaxLevel'] * length_conversion
            # 转换直径(英寸->米 或 毫米->米)
            self.Tanks_df['Diameter'] = self.Tanks_df['Diameter'] * length_conversion
            # 转换最小体积(立方英尺->立方米 或 立方米->立方米)
            self.Tanks_df['MinVolume'] = self.Tanks_df['MinVolume'] * (length_conversion ** 3)

        # 转换管道数据
        if not self.Pipes_df.empty:
            print(STEP + "正在进行管道数据换算...")
            # 转换长度(英尺->米 或 米->米)
            self.Pipes_df['Length'] = self.Pipes_df['Length'] * length_conversion
            # 转换直径(英寸->米 或 毫米->米)
            self.Pipes_df['Diameter'] = self.Pipes_df['Diameter'] * diameter_conversion

            # 根据水头损失公式转换粗糙度
            if headloss_formula == 'H_W':  # Hazen-Williams
                # H-W粗糙度无需转换，因为它是无量纲的
                pass
            elif headloss_formula == 'D_W':  # Darcy-Weisbach,未经过验证，不保证准确
                # D-W粗糙度单位为毫米，需要转换为米
                self.Pipes_df['Roughness'] = self.Pipes_df['Roughness'] * 0.001
            elif headloss_formula == 'C_M':  # Chezy-Manning,未经过验证，不保证准确
                # C-M粗糙度无需转换，因为它是无量纲的
                pass

        # 转换水泵数据
        if not self.Pumps_df.empty:
            print(STEP + "正在进行水泵数据换算...")
            # 对于功率型水泵，需要转换功率单位(马力->千瓦 或 千瓦->千瓦)
            # Power列存储的是功率值，而不是标识水泵类型的标志
            if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD']:
                # 马力 -> 千瓦
                power_conversion = 0.745699872
                # 转换Power列的值（功率值）
                self.Pumps_df['Power'] = self.Pumps_df['Power'].astype(float) * power_conversion

            # 注意：PumpCurve列存储的是水头-流量曲线的ID，不需要进行单位转换
            # EfficiencyCurve列存储的是效率曲线的ID，也不需要进行单位转换

        # 转换阀门数据
        if not self.Valves_df.empty:
            print(STEP + "正在进行阀门数据换算...")
            # 转换直径(英寸->米 或 毫米->米)
            self.Valves_df['Diameter'] = self.Valves_df['Diameter'] * diameter_conversion

            # 根据阀门类型转换设置值
            for idx, row in self.Valves_df.iterrows():
                valve_type = row['ValveType']
                if valve_type in ['PRV', 'PSV', 'PBV']:
                    # 压力型阀门，设置值为压力(psi->米水头 或 米水头->米水头)
                    if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD']:
                        # psi -> 米水头
                        pressure_psi_to_mh = 0.703070
                        self.Valves_df.at[idx, 'Setting'] = float(row['Setting']) * pressure_psi_to_mh
                elif valve_type == 'FCV':
                    # 流量控制阀，设置值为流量
                    if flow_units in flow_conversion:
                        self.Valves_df.at[idx, 'Setting'] = float(row['Setting']) * flow_conversion[flow_units]
                elif valve_type == 'TCV':
                    # 节流控制阀，设置值为损失系数(无量纲)，无需转换
                    pass

        # 转换曲线数据
        if not self.Curves_df.empty:
            print(STEP + "正在进行曲线数据换算...")
            for idx, row in self.Curves_df.iterrows():
                curve_type = row['Type']
                curve_values_str = row['CurveValues']

                # 解析曲线值
                curve_values = curve_values_str.strip('[]').split('][')
                new_curve_values = []

                for point in curve_values:
                    x, y = map(float, point.split(','))

                    if curve_type == 'Pump':
                        # 水泵曲线: x为流量，y为水头
                        x_new = x * flow_conversion[flow_units]  # 流量转换
                        y_new = y * length_conversion  # 水头转换
                    elif curve_type == 'Volume':
                        # 体积曲线: x为水位，y为体积
                        x_new = x * length_conversion  # 水位转换
                        y_new = y * (length_conversion ** 3)  # 体积转换
                    elif curve_type == 'Efficiency':
                        # 效率曲线: x为流量，y为效率(%)
                        x_new = x * flow_conversion[flow_units]  # 流量转换
                        y_new = y  # 效率无需转换
                    elif curve_type == 'Headloss':
                        # 水头损失曲线: x为流量，y为水头损失
                        x_new = x * flow_conversion[flow_units]  # 流量转换
                        y_new = y * length_conversion  # 水头损失转换
                    else:
                        # 其他类型曲线，保持原值
                        x_new, y_new = x, y

                    new_curve_values.append(f"{x_new},{y_new}")

                # 更新曲线值
                self.Curves_df.at[idx, 'CurveValues'] = '[' + ']['.join(new_curve_values) + ']'

        # 转换控制规则中的阈值
        if not self.Controls_df.empty:
            print(STEP + "正在进行控制规则中阈值的换算...")
            # 将控制规则拆分成列表
            self.Controls_df['split_control'] = self.Controls_df['Control'].str.split()

            # 处理IF控制规则
            if_controls = self.Controls_df[
                self.Controls_df['split_control'].apply(lambda x: len(x) > 3 and x[3] == 'IF')]

            # 创建节点类型字典，用于快速查找节点类型
            node_types = {}
            # 添加节点
            for _, row in self.Junctions_df.iterrows():
                node_types[str(row['Id'])] = 'Junction'
            # 添加水箱
            for _, row in self.Tanks_df.iterrows():
                node_types[str(row['Id'])] = 'Tank'
            # 添加水库
            for _, row in self.Reservoirs_df.iterrows():
                node_types[str(row['Id'])] = 'Reservoir'

            if not if_controls.empty:
                for idx, row in if_controls.iterrows():
                    print(DEBUG + f"{row}")
                    parts = row['split_control']
                    if len(parts) > 7 and (parts[6].upper() == 'ABOVE' or parts[6].upper() == 'BELOW'):
                        try:
                            # 获取节点ID和阈值
                            node_id = parts[5]
                            threshold = float(parts[7])

                            # 根据节点类型选择转换方式
                            node_type = node_types.get(node_id, 'Unknown')

                            if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD']:
                                if node_type == 'Junction':
                                    # 节点压力从psi转换为米水头
                                    threshold_converted = threshold * 0.703070
                                    print(
                                        INFO + f"节点 {node_id} 的压力阈值从 {threshold} psi 转换为 {threshold_converted} 米水头")
                                elif node_type == 'Tank':
                                    # 水箱水位从英尺转换为米
                                    threshold_converted = threshold * length_conversion
                                    print(
                                        INFO + f"水箱 {node_id} 的水位阈值从 {threshold} 英尺 转换为 {threshold_converted} 米")
                                elif node_type == 'Reservoir':
                                    # 水库水头从英尺转换为米
                                    threshold_converted = threshold * length_conversion
                                    print(
                                        INFO + f"水库 {node_id} 的水头阈值从 {threshold} 英尺 转换为 {threshold_converted} 米")
                                else:
                                    # 未知节点类型，保持不变并发出警告
                                    threshold_converted = threshold
                                    print(WARNING + f"警告: 未知节点类型 {node_id}，阈值保持不变: {threshold}")
                            else:
                                # 公制单位，保持不变
                                threshold_converted = threshold

                            # 更新阈值
                            parts[7] = str(threshold_converted)

                            # 重建控制规则
                            self.Controls_df.at[idx, 'Control'] = ' '.join(parts)
                        except (ValueError, IndexError) as e:
                            print(WARNING + f"警告: 无法转换控制规则阈值: {' '.join(parts)}, 错误: {str(e)}")

            # 删除临时列
            self.Controls_df.drop('split_control', axis=1, inplace=True)

        # 转换选项中的单位相关设置
        print(STEP + "正在进行选项设置数据换算...")

        # 最小压力和服务压力(psi->米水头 或 米水头->米水头)
        if flow_units in ['CFS', 'GPM', 'MGD', 'IMGD', 'AFD']:
            pressure_psi_to_mh = 0.703070
            self.options_dict['MinimumPressure'] = float(self.options_dict['MinimumPressure']) * pressure_psi_to_mh
            self.options_dict['ServicePressure'] = float(self.options_dict['ServicePressure']) * pressure_psi_to_mh

        # 更新流量单位为SI单位
        self.options_dict['FlowUnits'] = 'CMH'  # 立方米/小时，WNTR默认使用的单位

        print(SUCCESS + "单位转换完成")
        return self

    def to_wntr(self):
        def _add_pattern_information(wn):
            TimePatterns_df = self.TimePatterns_df.copy()
            for idx, row in tqdm(TimePatterns_df.iterrows(), total=len(TimePatterns_df), desc="添加模式"):
                # 过滤掉空字符串，只保留有效数值
                multipliers_str = row['Multipliers'].strip()  # 去除首尾空白
                if multipliers_str:  # 确保不是空字符串
                    pattern_values = np.array(
                        [float(x.strip()) for x in multipliers_str.split(',') if x.strip()],
                        dtype=np.float64)
                else:
                    print(Fore.LIGHTRED_EX + f"警告：发现空的 Multipliers 值")
                    pattern_values = np.array([], dtype=np.float64)

                wntr_pattern = wntr.network.elements.Pattern(row['ID'], pattern_values)
                wn.add_pattern(name=str(row['ID']),
                               pattern=wntr_pattern)
            return wn

        def _add_junction_information(wn):
            Junctions_df = self.Junctions_df.copy()
            Junctions_df = Junctions_df[Junctions_df['Enabled'] == 1]
            for idx, row in tqdm(Junctions_df.iterrows(), total=len(Junctions_df), desc="添加节点"):
                wn.add_junction(name=str(row['Id']),
                                base_demand=0.0 if pd.isna(row['BaseDemand']) else float(row['BaseDemand']),
                                demand_pattern=None if pd.isna(row['DemandPattern']) else str(row['DemandPattern']),
                                elevation=float(row['Elevation']),
                                coordinates=(float(row['X']), float(row['Y'])))
            return wn

        def _add_demand_information(wn):
            """添加额外需水量信息，过滤掉与基础需水量重复的记录"""
            external_demand_df = self.external_demand_df.copy()
            Junctions_df = self.Junctions_df.copy()
            print(STEP + "正在添加额外需水量...")
            print(DEBUG + f"原始 external_demand_df: {external_demand_df.head(3)}")

            # 创建一个包含节点基础需水量信息的数据框
            base_demands = []
            for _, row in Junctions_df.iterrows():
                if not pd.isna(row['BaseDemand']) and float(row['BaseDemand']) != 0.0:
                    base_demands.append({
                        'JunctionId': row['Id'],
                        'Demand': float(row['BaseDemand']),
                        'Pattern': row['DemandPattern'] if not pd.isna(row['DemandPattern']) else None
                    })

            base_demands_df = pd.DataFrame(base_demands)
            print(DEBUG + f"基础需水量信息: {base_demands_df.head(3) if not base_demands_df.empty else '无基础需水量'}")

            # 过滤掉与基础需水量重复的记录
            if not base_demands_df.empty:
                # 创建一个合并键，用于比较
                def create_key(row):
                    pattern = row['Pattern'] if not pd.isna(row['Pattern']) else 'None'
                    return f"{row['JunctionId']}_{row['Demand']}_{pattern}"

                base_demands_df['merge_key'] = base_demands_df.apply(create_key, axis=1)
                external_demand_df['merge_key'] = external_demand_df.apply(create_key, axis=1)

                # 找出重复的记录
                duplicate_keys = set(base_demands_df['merge_key']).intersection(set(external_demand_df['merge_key']))
                print(DEBUG + f"找到 {len(duplicate_keys)} 条重复的需水量记录")

                # 保存重复记录到Excel文件
                if duplicate_keys:
                    # 获取重复的记录
                    duplicate_demands_df = external_demand_df[
                        external_demand_df['merge_key'].isin(duplicate_keys)].copy()

                    # 创建ID映射字典，将新ID映射回原始ID
                    id_mapping = {}

                    # 从各个映射表中获取ID映射关系
                    for df_name in ['Junctions_map_df', 'Reservoirs_map_df', 'Tanks_map_df']:
                        if hasattr(self, df_name) and not getattr(self, df_name).empty:
                            df = getattr(self, df_name)
                            if 'Id' in df.columns and 'OriginalId' in df.columns:
                                id_map = dict(zip(df['Id'], df['OriginalId']))
                                id_mapping.update(id_map)

                    # 添加原始ID列
                    duplicate_demands_df['OriginalJunctionId'] = duplicate_demands_df['JunctionId'].map(
                        lambda x: id_mapping.get(str(x), str(x)))

                    # 保存到Excel文件
                    duplicate_file_path = 'duplicate_demands.xlsx'
                    duplicate_demands_df.to_excel(duplicate_file_path, index=False)
                    print(INFO + f"已将 {len(duplicate_demands_df)} 条重复需水量记录保存到 {duplicate_file_path}")

                    # 打印部分重复记录示例
                    print(DEBUG + f"重复需水量记录示例:\n{duplicate_demands_df.head(5)}")

                # 过滤掉重复记录
                filtered_demands_df = external_demand_df[~external_demand_df['merge_key'].isin(duplicate_keys)]
                filtered_demands_df = filtered_demands_df.drop('merge_key', axis=1)

                print(DEBUG + f"过滤后的 Demands_df: {filtered_demands_df.head(3)}")
                print(
                    INFO + f"从 {len(external_demand_df)} 条记录中过滤掉 {len(external_demand_df) - len(filtered_demands_df)} 条重复记录，剩余 {len(filtered_demands_df)} 条额外需水量记录")
            else:
                filtered_demands_df = external_demand_df
                print(INFO + "没有基础需水量信息，不需要过滤")

            # 按节点ID分组处理
            for junction_id, group in tqdm(filtered_demands_df.groupby('JunctionId'), desc="添加额外需水量"):
                junction_id_str = str(junction_id)

                # 获取节点对象
                try:
                    node = wn.get_node(junction_id_str)

                    # 打印添加前的需水量信息
                    # print(DEBUG + f"节点 {junction_id_str} 添加额外需水量前的需水量列表: {node.demand_timeseries_list}")

                    # 添加该节点的所有额外需水量
                    for _, row in group.iterrows():
                        demand = 0.0 if pd.isna(row['Demand']) else float(row['Demand'])
                        pattern = None if pd.isna(row['Pattern']) else str(row['Pattern'])

                        # 添加额外需水量
                        node.add_demand(base=demand, pattern_name=pattern)
                        # print(DEBUG + f"为节点 {junction_id_str} 添加额外需水量: {demand}, 模式: {pattern}")

                    # 打印添加后的需水量信息
                    # print(DEBUG + f"节点 {junction_id_str} 添加额外需水量后的需水量列表: {node.demand_timeseries_list}")

                except KeyError:
                    print(WARNING + f"警告: 找不到节点 {junction_id_str}，跳过添加额外需水量")
                except Exception as e:
                    print(ERROR + f"为节点 {junction_id_str} 添加额外需水量时出错: {str(e)}")

            return wn

        def _add_pipes_information(wn):
            Pipes_df = self.Pipes_df.copy()
            Pipes_df = Pipes_df[Pipes_df['Enabled'] == 1]
            for idx, row in tqdm(Pipes_df.iterrows(), total=len(Pipes_df), desc="添加管道"):
                if row['InitialStatus'] != 'CV':
                    wn.add_pipe(name=str(row['Id']),
                                start_node_name=str(row['StartNodeId']),
                                end_node_name=str(row['EndNodeId']),
                                length=float(row['Length']),
                                diameter=float(row['Diameter']),
                                roughness=float(row['Roughness']),
                                minor_loss=float(row['MinorLoss']),
                                initial_status=str(row['InitialStatus']),
                                check_valve=False)
                else:
                    wn.add_pipe(name=str(row['Id']),
                                start_node_name=str(row['StartNodeId']),
                                end_node_name=str(row['EndNodeId']),
                                length=float(row['Length']),
                                diameter=float(row['Diameter']),
                                roughness=float(row['Roughness']),
                                minor_loss=float(row['MinorLoss']),
                                initial_status='OPEN',
                                check_valve=True)
            return wn

        def _add_control_information(wn):
            def _parse_control_line(control_str, wn):
                """解析控制规则字符串，返回Control对象"""
                parts = control_str.split()
                if len(parts) < 4:
                    raise ValueError(f"控制规则格式错误: {control_str}")

                # 获取link对象
                link_id = parts[1]
                link = wn.get_link(link_id)

                # 将状态字符串转换为LinkStatus枚举值
                def get_status_value(status_str):
                    status_map = {
                        'OPEN': wntr.network.LinkStatus.Opened,
                        'CLOSED': wntr.network.LinkStatus.Closed
                    }
                    status_str = status_str.upper()
                    if status_str not in status_map:
                        raise ValueError(f"不支持的状态值: {status_str}")
                    return status_map[status_str]

                # 解析时间控制规则: LINK id status AT TIME t
                if 'TIME' in parts:
                    time_idx = parts.index('TIME') + 1
                    time_str = parts[time_idx]
                    if ':' in time_str:
                        time_parts = time_str.split(':')
                        if len(time_parts) == 2:
                            hours, minutes = map(int, time_parts)
                            time = hours + minutes / 60
                        elif len(time_parts) == 3:
                            hours, minutes, seconds = map(int, time_parts)
                            time = hours + minutes / 60 + seconds / 3600
                    else:
                        time = float(time_str)

                    # 解析状态并转换为枚举值
                    status = get_status_value(parts[2])

                    # 创建时间控制规则
                    return wntr.network.controls.ControlAction(link, 'status', status), \
                        wntr.network.controls.SimTimeCondition(wn, '=', time * 3600)

                # 解析条件控制规则: LINK id status IF NODE id ABOVE/BELOW value
                elif 'IF' in parts:
                    # 获取node对象
                    node_id = parts[5]
                    node = wn.get_node(node_id)

                    # 解析状态并转换为枚举值
                    status = get_status_value(parts[2])

                    # 解析条件
                    condition_type = parts[6].upper()
                    threshold = float(parts[7])

                    # 注意：阈值已经在units_change方法中转换过，这里不需要再次转换
                    if condition_type == 'ABOVE':
                        condition = wntr.network.controls.ValueCondition(node, 'pressure', '>', threshold)
                    elif condition_type == 'BELOW':
                        condition = wntr.network.controls.ValueCondition(node, 'pressure', '<', threshold)
                    else:
                        raise ValueError(f"不支持的条件类型: {condition_type}")

                    return wntr.network.controls.ControlAction(link, 'status', status), condition
                else:
                    raise ValueError(f"不支持的控制规则类型: {control_str}")

            Controls_df = self.Controls_df.copy()
            for idx, row in tqdm(Controls_df.iterrows(), total=len(Controls_df), desc="添加控制规则"):
                control_str = str(row['Control'])
                try:
                    # 解析控制规则字符串
                    action, condition = _parse_control_line(control_str, wn)
                    # 创建Control对象
                    control = wntr.network.controls.Control(condition, action, name=f"control_{idx}")
                    # 添加到水力模型
                    wn.add_control(f"control_{idx}", control)
                except Exception as e:
                    print(WARNING + f"警告: 控制规则 {control_str} 添加失败: {str(e)}")

            return wn

        def _add_curve_information(wn):
            Curves_df = self.Curves_df.copy()
            for idx, row in tqdm(Curves_df.iterrows(), total=len(Curves_df), desc="添加曲线"):
                # 解析曲线值字符串为元组列表
                curve_values = row['CurveValues'].strip('[]').split('][')
                xy_tuples = []
                for point in curve_values:
                    x, y = map(float, point.split(','))
                    xy_tuples.append((x, y))

                if str(row['Type']) == 'Volume':
                    wn.add_curve(name=str(row['ID']),
                                 curve_type='VOLUME',  # WaterDesk 5.0.0中Type为小写，需要转换为大写
                                 xy_tuples_list=xy_tuples)
                elif str(row['Type']) == 'Pump':
                    wn.add_curve(name=str(row['ID']),
                                 curve_type='HEAD',  # WaterDesk 5.0.0中Type为小写，需要转换为大写
                                 xy_tuples_list=xy_tuples)
                else:
                    raise ValueError(
                        "工具包暂时只有对Volume和Pump类型的曲线进行添加，其它曲线的添加在未来更新")
            return wn

        def _add_pump_information(wn):
            Pumps_df = self.Pumps_df.copy()
            Pumps_df = Pumps_df[Pumps_df['Enabled'] == 1]

            # 创建曲线类型字典，用于查找水泵类型
            curve_types = {}
            if not self.Curves_df.empty:
                for _, curve_row in self.Curves_df.iterrows():
                    curve_types[str(curve_row['ID'])] = str(curve_row['Type'])

            for idx, row in tqdm(Pumps_df.iterrows(), total=len(Pumps_df), desc="添加水泵"):
                # 确定水泵类型
                pump_curve_id = str(row['PumpCurve'])
                efficiency_curve_id = str(row.get('EfficiencyCurve', ''))

                # 默认为HEAD类型
                pump_type = 'HEAD'
                pump_parameter = pump_curve_id

                # 如果有效率曲线，则为POWER类型
                if efficiency_curve_id and efficiency_curve_id in curve_types and curve_types[
                    efficiency_curve_id] == 'Efficiency':
                    pump_type = 'POWER'
                    pump_parameter = str(row['Power'])  # 使用功率值作为参数
                # 如果有水泵曲线，确认是HEAD类型
                elif pump_curve_id in curve_types and curve_types[pump_curve_id] == 'Pump':
                    pump_type = 'HEAD'
                    pump_parameter = pump_curve_id

                wn.add_pump(name=str(row['Id']),
                            start_node_name=str(row['StartNodeId']),
                            end_node_name=str(row['EndNodeId']),
                            pump_type=pump_type,  # 根据曲线类型确定
                            pump_parameter=pump_parameter,  # 曲线ID或功率值
                            speed=float(row['Speed']),
                            pattern=str(row['Pattern']),
                            initial_status=str(row['InitialStatus']))
            return wn

        def _add_reservoir_information(wn):
            Reservoirs_df = self.Reservoirs_df.copy()
            Reservoirs_df = Reservoirs_df[Reservoirs_df['Enabled'] == 1]
            for idx, row in tqdm(Reservoirs_df.iterrows(), total=len(Reservoirs_df), desc="添加水库"):
                wn.add_reservoir(name=str(row['Id']),
                                 base_head=float(row['TotalHead']),
                                 head_pattern=str(row['HeadPattern']),
                                 coordinates=(float(row['X']), float(row['Y'])))
            return wn

        def _add_tank_information(wn):
            Tanks_df = self.Tanks_df.copy()
            Tanks_df = Tanks_df[Tanks_df['Enabled'] == 1]
            for idx, row in tqdm(Tanks_df.iterrows(), total=len(Tanks_df), desc="添加水箱"):
                if str(row['VolumeCurve']) == '':
                    wn.add_tank(name=str(row['Id']),
                                elevation=float(row['Elevation']),
                                init_level=float(row['InitialLevel']),
                                min_level=float(row['MinLevel']),
                                max_level=float(row['MaxLevel']),
                                diameter=float(row['Diameter']),
                                min_vol=float(row['MinVolume']),
                                vol_curve=None,
                                coordinates=(float(row['X']), float(row['Y'])))
                else:
                    wn.add_tank(name=str(row['Id']),
                                elevation=float(row['Elevation']),
                                init_level=float(row['InitialLevel']),
                                min_level=float(row['MinLevel']),
                                max_level=float(row['MaxLevel']),
                                diameter=float(row['Diameter']),
                                min_vol=float(row['MinVolume']),
                                vol_curve=str(row['VolumeCurve']),
                                coordinates=(float(row['X']), float(row['Y'])))
            return wn

        def _add_valve_information(wn):
            Valves_df = self.Valves_df.copy()
            Valves_df = Valves_df[Valves_df['Enabled'] == 1]
            for idx, row in tqdm(Valves_df.iterrows(), total=len(Valves_df), desc="添加阀门"):
                if str(row['FixedStatus']) == 'None':
                    wn.add_valve(name=str(row['Id']),
                                 start_node_name=str(row['StartNodeId']),
                                 end_node_name=str(row['EndNodeId']),
                                 diameter=float(row['Diameter']),
                                 valve_type=str(row['ValveType']),  # PRV, PSV, PBV, FCV, TCV, GPV
                                 minor_loss=0.0,  # WaterDesk 5.0.0版本中没有MinorLoss字段，这里假设为0
                                 initial_setting=0.0 if pd.isna(row['Setting']) else float(row['Setting']),
                                 initial_status='ACTIVE')
                elif str(row['FixedStatus']) == 'Open' or str(row['FixedStatus']) == 'Closed':
                    wn.add_valve(name=str(row['Id']),
                                 start_node_name=str(row['StartNodeId']),
                                 end_node_name=str(row['EndNodeId']),
                                 diameter=float(row['Diameter']),
                                 valve_type=str(row['ValveType']),  # PRV, PSV, PBV, FCV, TCV, GPV
                                 minor_loss=0.0,  # WaterDesk 5.0.0版本中没有MinorLoss字段，这里假设为0
                                 initial_setting=0.0 if pd.isna(row['Setting']) else float(row['Setting']),
                                 initial_status=str(row['FixedStatus'].upper()))
                else:
                    raise ValueError(
                        "表Valves中的FixedStatus字段不是None、Open或Closed，与预期不符，请检查数据是否来自于正确的WaterDesk 5.0.0版本。")
            return wn

        def _add_vertices_information(wn):
            """添加管道折点信息"""
            Vertices_df = self.Vertices_df.copy()

            if Vertices_df.empty:
                print(INFO + "折点表为空，跳过添加折点信息")
                return wn

            # 按管道ID分组处理折点
            for pipe_id, group in tqdm(Vertices_df.groupby('Id'), desc="添加折点"):
                try:
                    # 尝试不同格式的ID
                    pipe_id_str = str(pipe_id).strip()  # 去除可能的空格

                    # 尝试直接获取管道
                    try:
                        pipe = wn.get_link(pipe_id_str)
                    except KeyError:
                        # 如果找不到，尝试转换为小写
                        raise KeyError(f"找不到ID为 {pipe_id_str} 的管道，无法处理折点信息")

                    # 提取折点坐标 - 修改这部分以适应没有Order列的情况
                    vertices = []
                    # 由于没有Order列，我们直接使用数据框中的顺序
                    for _, row in group.iterrows():
                        vertices.append((float(row['X']), float(row['Y'])))

                    # 设置管道的折点
                    pipe.vertices = vertices

                except KeyError as e:
                    print(WARNING + f"警告: {str(e)}")
                except Exception as e:
                    print(ERROR + f"为管道 {pipe_id} 添加折点时出错: {str(e)}")
            return wn

        def _add_options_information(wn):
            """添加模型选项设置"""

            def _add_hydraulic_options():
                """添加水力计算选项"""
                # 水头损失公式映射
                headloss_map = {
                    'H_W': 'H-W',  # Hazen-Williams
                    'D_W': 'D-W',  # Darcy-Weisbach
                    'C_M': 'C-M'  # Chezy-Manning
                }
                headloss = headloss_map.get(self.options_dict['HeadlossFormular'])
                wn.options.hydraulic.headloss = str(headloss)
                wn.options.hydraulic.hydraulics = None  # 截止到WaterDesk 5.0.0版本，没有Hydraulics选项，这里默认为None
                wn.options.hydraulic.hydraulics_filename = 'None'  # 截止到WaterDesk 5.0.0版本，没有HydraulicsFilename选项，这里默认为None
                wn.options.hydraulic.viscosity = float(self.options_dict['RelativeViscosity'])
                wn.options.hydraulic.specific_gravity = float(self.options_dict['SpecificGravity'])
                wn.options.hydraulic.pattern = None if self.options_dict['DefaultPattern'] == '' else str(
                    self.options_dict['DefaultPattern'])
                wn.options.hydraulic.demand_multiplier = float(self.options_dict['DemandMultiplier'])
                wn.options.hydraulic.emitter_exponent = float(self.options_dict['EmitterExponent'])
                wn.options.hydraulic.minimum_pressure = float(self.options_dict['MinimumPressure'])
                wn.options.hydraulic.required_pressure = float(self.options_dict['ServicePressure'])
                wn.options.hydraulic.pressure_exponent = float(self.options_dict['PressureExponent'])

                # 设置流量单位为CMH（立方米/小时），与units_change方法中的设置保持一致
                wn.options.hydraulic.inpfile_units = 'CMH'

                # 添加其他未重复的选项
                wn.options.hydraulic.trials = int(self.options_dict['MaximumTrials'])
                wn.options.hydraulic.accuracy = float(self.options_dict['Accuracy'])
                wn.options.hydraulic.flowchange = 0.0  # 截止到WaterDesk 5.0.0版本，没有Flowchange选项，这里默认为0.0
                wn.options.hydraulic.unbalanced = str(self.options_dict['IfUnbalanced'].upper())
                wn.options.hydraulic.unbalanced_value = None  # 截止到WaterDesk 5.0.0版本，没有UnbalancedValue选项，这里默认为None，这里参数是int类型
                wn.options.hydraulic.checkfreq = 2  # 截止到WaterDesk 5.0.0版本，没有Checkfreq选项，这里默认为2
                wn.options.hydraulic.maxcheck = 10  # 截止到WaterDesk 5.0.0版本，没有Maxcheck选项，这里默认为10
                wn.options.hydraulic.damplimit = 0.0  # 截止到WaterDesk 5.0.0版本，没有Damplimit选项，这里默认为0.0
                dmand_model_map = {
                    'FIXED': 'DD',
                    'CONSTRAINED': 'wntr_not_supported',
                    'POWER': 'PDD',
                    'LOGISTIC': 'wntr_not_supported',
                }
                demand_model = dmand_model_map.get(self.options_dict['DemandModel'])
                if demand_model == 'wntr_not_supported':
                    raise ValueError(
                        f"WaterDesk 5.0.0版本不支持DemandModel选项中的{self.options_dict['DemandModel']}，请使用其他版本的WaterDesk。")
                else:
                    wn.options.hydraulic.demand_model = str(demand_model)
                wn.options.hydraulic.inpfile_pressure_units = 'None'  # 截止到WaterDesk 5.0.0版本，没有InpfilePressureUnits选项，这里默认为None

            def _add_energy_options():
                """添加能量选项"""
                wn.options.energy.global_price = float(self.options_dict['EnergyPrice'])
                wn.options.energy.global_pattern = None if self.options_dict['PricePattern'] == '' else str(
                    self.options_dict['PricePattern'])
                wn.options.energy.global_efficiency = float(self.options_dict['PumpEfficiency'])
                wn.options.energy.demand_charge = float(self.options_dict['DemandCharge'])

            def _add_Quality_options():
                """添加水质选项"""
                wn.options.quality.parameter = str(self.options_dict['QualityParameter'].upper())
                wn.options.quality.trace_node = str(self.options_dict['TraceNode'])
                wn.options.quality.chemical_name = 'CHEMICAL'  # 截止到WaterDesk 5.0.0版本，没有chemical选项，这里默认为CHEMICAL
                wn.options.quality.diffusivity = float(self.options_dict['RelativeDiffusivity'])
                wn.options.quality.tolerance = float(self.options_dict['QualityTolerance'])
                mass_units_map = {
                    'mgPerL': 'mg/L',
                    'ugPerL': 'ug/L'
                }
                mass_units = mass_units_map.get(self.options_dict['MassUnits'])
                wn.options.quality.inpfile_units = str(mass_units)

            def _add_reaction_options():
                """添加反应选项"""
                wn.options.reaction.bulk_order = float(self.options_dict['BulkReactionOrder'])
                wall_order_map = {
                    'First': 1.0,
                    'Zero': 0.0,
                }
                wall_order = wall_order_map.get(self.options_dict['WallReactionOrder'])
                wn.options.reaction.wall_order = float(wall_order)
                wn.options.reaction.tank_order = float(self.options_dict['TankReactionOrder'])
                wn.options.reaction.bulk_coeff = float(self.options_dict['GlobalBulkCoeff'])
                wn.options.reaction.wall_coeff = float(self.options_dict['GlobalWallCoeff'])
                wn.options.reaction.limiting_potential = float(self.options_dict['LimitingConcentration'])
                wn.options.reaction.roughness_correl = float(self.options_dict['WallCoeffCorrelation'])

            def _add_time_option():
                """添加时间选项"""
                wn.options.time.duration = int(self.options_dict['TotalDuration'])
                wn.options.time.hydraulic_timestep = int(self.options_dict['HydraulicTimeStep'])
                wn.options.time.quality_timestep = int(self.options_dict['QualityTimeStep'])
                wn.options.time.rule_timestep = int(self.options_dict['PatternTimeStep'])
                wn.options.time.pattern_timestep = int(self.options_dict['PatternTimeStep'])
                wn.options.time.pattern_start = int(self.options_dict['PatternStartTime'])
                wn.options.time.report_timestep = int(self.options_dict['ReportingTimeStep'])
                wn.options.time.report_start = int(self.options_dict['ReportStartTime'])
                wn.options.time.start_clocktime = int(self.options_dict['ClockStartTime'])
                wn.options.time.statistic = str(self.options_dict['Statistic'])
                wn.options.time.pattern_interpolation = False

            _add_hydraulic_options()
            _add_energy_options()
            _add_Quality_options()
            _add_reaction_options()
            _add_time_option()

            return wn

        # 在to_wntr方法的最后，添加options设置
        wn = wntr.network.WaterNetworkModel()
        wn = _add_curve_information(wn) if not self.Curves_df.empty else wn  # 曲线需要在组件之前添加
        wn = _add_junction_information(wn) if not self.Junctions_df.empty else wn
        wn = _add_reservoir_information(wn) if not self.Reservoirs_df.empty else wn
        wn = _add_tank_information(wn) if not self.Tanks_df.empty else wn
        wn = _add_pipes_information(wn) if not self.Pipes_df.empty else wn
        wn = _add_pump_information(wn) if not self.Pumps_df.empty else wn
        wn = _add_valve_information(wn) if not self.Valves_df.empty else wn
        wn = _add_pattern_information(wn) if not self.TimePatterns_df.empty else wn
        wn = _add_demand_information(wn) if not self.Demands_df.empty or not self.external_demand_df.empty else wn
        wn = _add_options_information(wn)
        wn = _add_control_information(wn) if not self.Controls_df.empty else wn
        wn = _add_vertices_information(wn) if not self.Vertices_df.empty else wn
        return wn

    def to_inp(self, filename: str = 'output.inp', version: float = 2.2) -> None:
        """
        将水力模型保存为 EPANET INP 文件格式

        参数:
            filename (str): 输出文件名，必须以 .inp 结尾
            version (float): EPANET 版本号，支持 2.0 或 2.2

        异常:
            TypeError: 参数类型错误
            ValueError: 参数值无效
            OSError: 文件写入错误
        """

        def _modify_inp_controls(inp_file_path):
            """
            修改INP文件中的控制规则格式，将具体组件类型(如Pump、Tank)替换为EPANET标准的LINK和NODE

            Args:
                inp_file_path (str): INP文件路径

            Returns:
                str: 修改后的INP文件路径
            """
            print(STEP + "正在修改控制规则格式...")

            # 读取INP文件内容
            with open(inp_file_path, 'r') as f:
                lines = f.readlines()

            # 标记是否在[CONTROLS]部分
            in_controls_section = False
            modified_lines = []

            # 组件类型映射
            node_types = ['JUNCTION', 'RESERVOIR', 'TANK']  # 节点类型组件
            link_types = ['PIPE', 'PUMP', 'VALVE']  # 链接类型组件

            for line in lines:
                # 检查是否进入或离开[CONTROLS]部分
                if line.strip() == '[CONTROLS]':
                    in_controls_section = True
                    modified_lines.append(line)
                    continue
                elif line.strip() == '' and in_controls_section:
                    # 空行可能表示部分结束
                    in_controls_section = False
                    modified_lines.append(line)
                    continue
                elif line.strip().startswith('[') and line.strip().endswith(']'):
                    # 新的部分开始
                    in_controls_section = False
                    modified_lines.append(line)
                    continue

                # 如果在[CONTROLS]部分，修改控制规则格式
                if in_controls_section:
                    parts = line.strip().split()
                    if len(parts) >= 8 and 'IF' in parts:
                        # 条件控制规则: COMPONENT_TYPE ID STATUS IF COMPONENT_TYPE ID ABOVE/BELOW VALUE
                        component_type = parts[0].upper()
                        # 检查第一个组件类型是否需要替换为LINK
                        if component_type in link_types:
                            parts[0] = 'LINK'
                        elif component_type in node_types:
                            parts[0] = 'NODE'

                        # 查找IF后面的组件类型
                        if_index = parts.index('IF')
                        if if_index + 1 < len(parts):
                            condition_component_type = parts[if_index + 1].upper()
                            # 检查条件组件类型是否需要替换为NODE
                            if condition_component_type in node_types:
                                parts[if_index + 1] = 'NODE'
                            elif condition_component_type in link_types:
                                parts[if_index + 1] = 'LINK'

                        # 重建控制规则行
                        modified_line = ' '.join(parts) + '\n'
                        modified_lines.append(modified_line)
                    elif len(parts) >= 5 and 'TIME' in parts:
                        # 时间控制规则: COMPONENT_TYPE ID STATUS AT TIME VALUE
                        component_type = parts[0].upper()
                        # 检查组件类型是否需要替换为LINK
                        if component_type in link_types:
                            parts[0] = 'LINK'
                        elif component_type in node_types:
                            parts[0] = 'NODE'  # 默认替换为LINK

                        # 重建控制规则行
                        modified_line = ' '.join(parts) + '\n'
                        modified_lines.append(modified_line)
                    else:
                        # 其他格式的控制规则，保持不变
                        modified_lines.append(line)
                else:
                    # 不在[CONTROLS]部分，保持不变
                    modified_lines.append(line)

            # 写入修改后的内容到原文件
            with open(inp_file_path, 'w') as f:
                f.writelines(modified_lines)

        # 检查文件名参数
        if not isinstance(filename, str):
            raise TypeError("filename 必须是字符串类型")
        if not filename.lower().endswith('.inp'):
            raise ValueError("filename 必须以 .inp 结尾")
        # 检查版本号参数
        if not isinstance(version, (int, float)):
            raise TypeError("version 必须是数字类型")
        if version not in [2.0, 2.2]:
            raise ValueError("version 只支持 2.0 或 2.2")

        try:
            wn = self.to_wntr()
            wntr.network.write_inpfile(wn, filename, version)
            # 修改控制规则格式
            _modify_inp_controls(filename)
        except Exception as e:
            raise OSError(f"写入 INP 文件时发生错误: {str(e)}")